package xueqiu

import (
	"io/ioutil"
	logger "log"
	"net/http"
	"strings"

	"gitee.com/lenchu/stock-api-go/api"
	"gitee.com/lenchu/stock-api-go/utils"

	"github.com/gookit/goutil/arrutil"
	"github.com/tidwall/gjson"
)

const (
	base_url              = "https://xueqiu.com"
	header_set_cookie     = "Set-Cookie"
	header_cookie         = "Cookie"
	header_cookie_token   = "xq_a_token"
	cookie_separator      = ";"
	search_url            = "https://xueqiu.com/stock/search.json?code="
	get_by_code_url       = "https://stock.xueqiu.com/v5/stock/quote.json?symbol="
	batch_get_by_code_url = "https://stock.xueqiu.com/v5/stock/batch/quote.json?symbol="

	logger_prefix = "[Xueqiu] "
)

var log = logger.New(logger.Writer(), logger_prefix, logger.Flags())

type StockService struct {
	Client      *http.Client
	tokenKeeper utils.ObjectKeeper[string]
}

func (this *StockService) getHttpClient() *http.Client {
	if this.Client != nil {
		return this.Client
	}
	return http.DefaultClient
}
func (this *StockService) requestToken() string {
	client := this.getHttpClient()
	resp, err := client.Get(base_url)
	if err != nil {
		return ""
	}
	cookies := resp.Header.Values(header_set_cookie)
	for _, cookie := range cookies {
		if strings.Contains(cookie, header_cookie_token) {
			token := strings.Split(cookie, cookie_separator)[0]
			return token
		}
	}
	return ""
}
func (this *StockService) getWithRetry(url string) (resp *http.Response, err error) {
	req, err := http.NewRequest(http.MethodGet, url, nil)
	if err != nil {
		return
	}
	currentToken := this.tokenKeeper.Get()
	req.Header.Add(header_cookie, currentToken)
	client := this.getHttpClient()
	resp, err = client.Do(req)
	if resp.StatusCode == http.StatusBadRequest {
		_, newToken := this.tokenKeeper.ComputeWhen(
			func(currentValue string) bool {
				return currentValue == currentToken
			},
			func(oldValue string) string {
				return this.requestToken()
			},
		)
		if currentToken != newToken && newToken != "" {
			return this.getWithRetry(url)
		} else {
			log.Panicln("refresh token failed")
		}
	}
	return
}

func (this *StockService) GetByCode(code string) *api.Stock {
	xc := FromStandardCodeToXueqiuCode(code)
	if xc == nil {
		return nil
	}
	if xc.isJJ() {
		return this.getJJ(xc)
	}
	url := get_by_code_url + xc.symbol
	log.Println(url)
	resp, err := this.getWithRetry(url)
	if err != nil {
		return nil
	}
	byteBody, err := ioutil.ReadAll(resp.Body)
	strBody := string(byteBody)
	stockJson := gjson.Get(strBody, "data.quote")
	stock := api.Stock{
		Code:       code,
		Name:       stockJson.Get("name").String(),
		Price:      stockJson.Get("current").String(),
		UpdateTime: utils.TimestampToTime(stockJson.Get("timestamp").String()),
	}
	return &stock
}
func (this *StockService) SearchStock(keyword string) []*api.Stock {
	url := search_url + keyword
	log.Println(url)
	resp, err := this.getWithRetry(url)
	if err != nil {
		return make([]*api.Stock, 0)
	}
	byteBody, err := ioutil.ReadAll(resp.Body)
	strBody := string(byteBody)
	searchResultList := gjson.Get(strBody, "stocks").Array()
	resultList := make([]*api.Stock, len(searchResultList))
	idx := 0
	for _, result := range searchResultList {
		codeType := result.Get("type").Int()
		name := result.Get("name").String()
		code := result.Get("code").String()
		xc := FromSymbolAndTypeToXueqiuCode(codeType, code)
		if xc == nil {
			continue
		}
		standardCode := xc.standardCode
		resultList[idx] = &api.Stock{
			Name: name,
			Code: standardCode,
		}
		idx++
	}
	return resultList[0:idx]
}
func (this *StockService) BatchGetByCode(codes []string) map[string]*api.Stock {
	symbols := arrutil.StringsMap(codes, func(standardCode string) string {
		xc := FromStandardCodeToXueqiuCode(standardCode)
		if xc == nil {
			return ""
		}
		return xc.symbol
	})
	url := batch_get_by_code_url + arrutil.StringsJoin(",", symbols...)
	log.Println(url)
	resp, err := this.getWithRetry(url)
	if err != nil {
		return nil
	}
	byteBody, err := ioutil.ReadAll(resp.Body)
	strBody := string(byteBody)

	resultMap := make(map[string]*api.Stock)
	resultJsonArr := gjson.Get(strBody, "data.items").Array()
	for _, resultJson := range resultJsonArr {
		stockJson := resultJson.Get("quote")
		xc := FromSymbolAndTypeToXueqiuCode(stockJson.Get("type").Int(), stockJson.Get("symbol").String())
		var stock *api.Stock
		if xc.isJJ() {
			stock = this.getJJ(xc)
		} else {
			stock = &api.Stock{
				Code:       xc.standardCode,
				Name:       stockJson.Get("name").String(),
				Price:      stockJson.Get("current").String(),
				UpdateTime: utils.TimestampToTime(stockJson.Get("timestamp").String()),
			}
		}
		resultMap[xc.standardCode] = stock
	}
	return resultMap
}
